﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;

namespace AutomaTones.Model.Tools {
    /// <author>Anders Øland / anoe.itu.dk</author>
    /// <summary>
    /// A few mathematical tool medthods.
    /// </summary>
    public static class Math {
        /// <summary>
        /// Converts unsigned integer to any base.
        /// NOTE: result is given in an array of int's - so eg. hex numbers are represented by digits of 0-15
        /// </summary>
        /// <param name="intNum"></param>
        /// <param name="baseVal"></param>
        /// <param name="digits">specific number of digits for result (optional)</param>
        /// <returns></returns>
        public static int[] DecimalToBase(uint intNum, uint baseVal, uint digits = 0u) {
            Contract.Requires(0 <= intNum && 0 < baseVal);
            Contract.Ensures(Contract.ForAll(Contract.Result<int[]>(), n => 0 <= n && baseVal > n));
            Contract.Ensures(digits <= Contract.Result<int[]>().Length);
            return DecimalToBase((ulong) intNum, baseVal, digits);
        }

        /// <summary>
        /// Converts unsigned long to any base.
        /// NOTE: result is given in an array of int's - so eg. hex numbers are represented by digits of 0-15
        /// </summary>
        /// <param name="longNum"></param>
        /// <param name="baseVal"></param>
        /// <param name="digits">specific number of digits for result (optional)</param>
        /// <returns></returns>
        [Pure] // Tagged as pure since its being called from other contracts which I dont want to loosen 
        public static int[] DecimalToBase(ulong longNum, uint baseVal, uint digits = 0u) {
            Contract.Requires(0 <= longNum && 0 < baseVal);
            Contract.Ensures(digits <= Contract.Result<int[]>().Length);
            var result = new LinkedList<int>();
            while (longNum > 0) {
                result.AddLast((int) (longNum % baseVal));
                longNum /= baseVal;
            }
            while (digits > result.Count) result.AddLast(0);
            return result.ToArray();
        }

        /// <summary>
        /// Converts n digits of any base to decimal.
        /// </summary>
        /// <param name="baseNum"></param>
        /// <param name="baseVal"></param>
        /// <returns></returns>
        [Pure] // Tagged as pure since its being called from other contracts which I dont want to loosen 
        public static uint BaseToDecimal(int[] baseNum, uint baseVal) {
            Contract.Requires(null != baseNum);
            Contract.Requires(0 < baseNum.Length);
            Contract.Requires(0 < baseVal);
            return (uint) baseNum.Select((t, i) => (t * (int) System.Math.Pow(baseVal, baseNum.Length - i - 1))).Sum();
        }
    }
}